package utils

import (
	"github.com/golang/protobuf/proto"
	cb "github.com/hyperledger/fabric-protos-go/common"
	rw "github.com/hyperledger/fabric-protos-go/ledger/rwset"
	kvrw "github.com/hyperledger/fabric-protos-go/ledger/rwset/kvrwset"
	cp "github.com/hyperledger/fabric-protos-go/peer"
)

type ObjIDsInBlock struct {
	BlockID uint64   `json:"blockid"`
	IDs     []string `json:"ids"`
}

func StrHashToNum(str string, mod int) int {
	var sum int
	for _, s := range str {
		sum += int(s)
	}
	return sum % mod
}

func ExtractObjIDsFromBlock(Block *cb.Block) (*ObjIDsInBlock, error) {
	objIDsInBlock := &ObjIDsInBlock{0, make([]string, 0)}
	envs, err := GetEnvelopesFromBlock(Block)
	if err != nil {
		return nil, err
	}
	for _, env := range envs {
		payload, err := GetPayloadFromEnvelope(env)
		if err != nil {
			return nil, err
		}
		transaction, err := GetTransactionFromPayload(payload)
		if err != nil {
			return nil, err
		}
		for _, transactionAction := range transaction.Actions {
			chaincodeActionPayload, err := GetChaincodeActionPayloadFromTransactionAction(transactionAction)
			if err != nil {
				return nil, err
			}
			proposalResponsePayload, err := GetProposalResponsePayloadFromChaincodeEndorsedAction(chaincodeActionPayload.Action)
			if err != nil {
				return nil, err
			}
			chaincodeAction, err := GetChaincodeActionFromProposalResponsePayload(proposalResponsePayload)
			if err != nil {
				return nil, err
			}
			txReadWriteSet, err := GetTxReadWriteSetFromChaincodeAction(chaincodeAction)
			if err != nil {
				return nil, err
			}
			for _, nsRwset := range txReadWriteSet.NsRwset {
				rwset, err := GetKVRWSetFromNsReadWriteSet(nsRwset)
				if err != nil {
					return nil, err
				}
				for _, write := range rwset.Writes {
					// fmt.Println(write.Key)
					objIDsInBlock.IDs = append(objIDsInBlock.IDs, write.Key)
				}
			}
		}
	}
	objIDsInBlock.BlockID = Block.Header.GetNumber()
	return objIDsInBlock, nil
}

// type Block struct {
// 	Header               *BlockHeader   `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
// 	Data                 *BlockData     `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
// 	Metadata             *BlockMetadata `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"`
// }

func GetEnvelopesFromBlock(block *cb.Block) ([]*cb.Envelope, error) {
	var envs []*cb.Envelope = make([]*cb.Envelope, 0)
	datas := block.Data.Data
	for i, data := range datas {
		envs = append(envs, nil)
		envs[i] = &cb.Envelope{}
		if err := proto.Unmarshal(data, envs[i]); err != nil {
			return nil, err
		}
	}

	return envs, nil
}

// type Envelope struct {
// 	Payload   []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
// 	Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
// }

func GetPayloadFromEnvelope(env *cb.Envelope) (*cb.Payload, error) {
	payload := &cb.Payload{}
	if err := proto.Unmarshal(env.Payload, payload); err != nil {
		return nil, err
	}
	return payload, nil
}

// type Payload struct {
// 	Header *Header `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
// 	Data   []byte  `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
// }

func GetTransactionFromPayload(payload *cb.Payload) (*cp.Transaction, error) {
	transaction := &cp.Transaction{}
	if err := proto.Unmarshal(payload.Data, transaction); err != nil {
		return nil, err
	}
	return transaction, nil
}

// type Transaction struct {
// 	Actions []*TransactionAction `protobuf:"bytes,1,rep,name=actions,proto3" json:"actions,omitempty"`
// }

// type TransactionAction struct {
// 	Header  []byte `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
// 	Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
// }

func GetChaincodeActionPayloadFromTransactionAction(transactionAction *cp.TransactionAction) (*cp.ChaincodeActionPayload, error) {
	chaincodeActionPayload := &cp.ChaincodeActionPayload{}
	if err := proto.Unmarshal(transactionAction.Payload, chaincodeActionPayload); err != nil {
		return nil, err
	}
	return chaincodeActionPayload, nil
}

// type ChaincodeActionPayload struct {
// 	ChaincodeProposalPayload []byte                   `protobuf:"bytes,1,opt,name=chaincode_proposal_payload,json=chaincodeProposalPayload,proto3" json:"chaincode_proposal_payload,omitempty"`
// 	Action                   *ChaincodeEndorsedAction `protobuf:"bytes,2,opt,name=action,proto3" json:"action,omitempty"`
// }

// type ChaincodeEndorsedAction struct {
// 	ProposalResponsePayload []byte         `protobuf:"bytes,1,opt,name=proposal_response_payload,json=proposalResponsePayload,proto3" json:"proposal_response_payload,omitempty"`
// 	Endorsements            []*Endorsement `protobuf:"bytes,2,rep,name=endorsements,proto3" json:"endorsements,omitempty"`
// }

func GetProposalResponsePayloadFromChaincodeEndorsedAction(chaincodeEndorsedAction *cp.ChaincodeEndorsedAction) (*cp.ProposalResponsePayload, error) {
	proposalResponsePayload := &cp.ProposalResponsePayload{}
	if err := proto.Unmarshal(chaincodeEndorsedAction.ProposalResponsePayload, proposalResponsePayload); err != nil {
		return nil, err
	}
	return proposalResponsePayload, nil
}

// type ProposalResponsePayload struct {
// 	ProposalHash []byte `protobuf:"bytes,1,opt,name=proposal_hash,json=proposalHash,proto3" json:"proposal_hash,omitempty"`
// 	Extension    []byte `protobuf:"bytes,2,opt,name=extension,proto3" json:"extension,omitempty"`
// }

func GetChaincodeActionFromProposalResponsePayload(proposalResponsePayload *cp.ProposalResponsePayload) (*cp.ChaincodeAction, error) {
	chaincodeAction := &cp.ChaincodeAction{}
	if err := proto.Unmarshal(proposalResponsePayload.Extension, chaincodeAction); err != nil {
		return nil, err
	}
	return chaincodeAction, nil
}

// type ChaincodeAction struct {
// 	Results     []byte       `protobuf:"bytes,1,opt,name=results,proto3" json:"results,omitempty"`
// 	Events      []byte       `protobuf:"bytes,2,opt,name=events,proto3" json:"events,omitempty"`
// 	Response    *Response    `protobuf:"bytes,3,opt,name=response,proto3" json:"response,omitempty"`
// 	ChaincodeId *ChaincodeID `protobuf:"bytes,4,opt,name=chaincode_id,json=chaincodeId,proto3" json:"chaincode_id,omitempty"`
// }

func GetTxReadWriteSetFromChaincodeAction(chaincodeAction *cp.ChaincodeAction) (*rw.TxReadWriteSet, error) {
	txReadWriteSet := &rw.TxReadWriteSet{}
	if err := proto.Unmarshal(chaincodeAction.Results, txReadWriteSet); err != nil {
		return nil, err
	}
	return txReadWriteSet, nil
}

// type TxReadWriteSet struct {
// 	DataModel TxReadWriteSet_DataModel `protobuf:"varint,1,opt,name=data_model,json=dataModel,proto3,enum=rwset.TxReadWriteSet_DataModel" json:"data_model,omitempty"`
// 	NsRwset   []*NsReadWriteSet        `protobuf:"bytes,2,rep,name=ns_rwset,json=nsRwset,proto3" json:"ns_rwset,omitempty"`
// }

// type NsReadWriteSet struct {
// 	Namespace             string                          `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
// 	Rwset                 []byte                          `protobuf:"bytes,2,opt,name=rwset,proto3" json:"rwset,omitempty"`
// 	CollectionHashedRwset []*CollectionHashedReadWriteSet `protobuf:"bytes,3,rep,name=collection_hashed_rwset,json=collectionHashedRwset,proto3" json:"collection_hashed_rwset,omitempty"`
// }

func GetKVRWSetFromNsReadWriteSet(nsReadWriteSet *rw.NsReadWriteSet) (*kvrw.KVRWSet, error) {
	kvrwset := &kvrw.KVRWSet{}
	if err := proto.Unmarshal(nsReadWriteSet.Rwset, kvrwset); err != nil {
		return nil, err
	}
	return kvrwset, nil
}

// type KVRWSet struct {
// 	Reads            []*KVRead          `protobuf:"bytes,1,rep,name=reads,proto3" json:"reads,omitempty"`
// 	RangeQueriesInfo []*RangeQueryInfo  `protobuf:"bytes,2,rep,name=range_queries_info,json=rangeQueriesInfo,proto3" json:"range_queries_info,omitempty"`
// 	Writes           []*KVWrite         `protobuf:"bytes,3,rep,name=writes,proto3" json:"writes,omitempty"`
// 	MetadataWrites   []*KVMetadataWrite `protobuf:"bytes,4,rep,name=metadata_writes,json=metadataWrites,proto3" json:"metadata_writes,omitempty"`
// }

// type KVWrite struct {
// 	Key      string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
// 	IsDelete bool   `protobuf:"varint,2,opt,name=is_delete,json=isDelete,proto3" json:"is_delete,omitempty"`
// 	Value    []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"`
// }
